/*
 * Canvas.java
 * 
 * Created by demory on Mar 28, 2009, 12:34:51 PM
 * 
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fpdev.apps.cart.gui.map;

import org.fpdev.apps.cart.gui.*;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Point2D;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JPanel;
import org.fpdev.util.gui.MapCoordinates;
import org.fpdev.apps.cart.CartEvent;
import org.fpdev.apps.cart.CartEventProcessor;
import org.fpdev.apps.cart.Editor;
import org.fpdev.apps.cart.network.AnchorPoint;
import org.fpdev.apps.cart.network.Corridor;
import org.fpdev.apps.cart.network.Line;

/**
 *
 * @author demory
 */
public class Canvas extends JPanel implements MouseWheelListener {

  //private Cartographer cart_;
  //private CartNetwork network_;
  private Editor ed_;
  private CartGUI gui_;
  private CartEventProcessor cep_;

  private MapCoordinates coords_;
  private Graphics2D g2d_;

  private int mx_, my_;
  private DragMode dragMode_;
  private AnchorPoint draggingPoint_;
  private Point2D.Double oldDragPtLoc_; // the original location of a dragged point (for undo purposes)

  // non-network components:
  private Set<Drawable> highlightedItems_;

  int clickTol_ = 5; // click tolerance in pixels

  enum DragMode { NONE, PANNING, DRAG_POINT }

  public Canvas(CartEventProcessor ceh, Editor ed, CartGUI gui) {
    super();
    //cart_ = cart;
    //network_ = cart.getNetwork();
    cep_ = ceh;
    ed_ = ed;
    gui_ = gui;
   
    dragMode_ = DragMode.NONE;

    highlightedItems_ = new HashSet<Drawable>();

    addMouseListener(new MouseAdapter() {

      @Override
      public void mouseClicked(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON1) {
          leftClick(e.getX(), e.getY(), e.isShiftDown());
        } else if (e.getButton() == MouseEvent.BUTTON3) {
          rightClick(e.getX(), e.getY(), e.isShiftDown());
        }
      }

      @Override
      public void mousePressed(MouseEvent e) {
        mx_ = e.getX();
        my_ = e.getY();
        if(e.getButton() == MouseEvent.BUTTON1) {
          AnchorPoint pt = ed_.getDocument().getNetwork().getPointAtXY(coords_.xToWorld(mx_), coords_.yToWorld(my_), coords_.dxToWorld(clickTol_));
          if(pt != null) {
            dragMode_ = DragMode.DRAG_POINT;
            draggingPoint_ = pt;
            oldDragPtLoc_ = new Point2D.Double(pt.getX(), pt.getY());
          }
        }
        else if(e.getButton() == MouseEvent.BUTTON3) {
          dragMode_ = DragMode.PANNING;
        }
      }

      @Override
      public void mouseReleased(MouseEvent e) {
        if(draggingPoint_ != null && draggingPoint_.getX()-oldDragPtLoc_.x != 0 && draggingPoint_.getY()-oldDragPtLoc_.y != 0) {
          CartEvent ev = new CartEvent(CartEvent.Type.MOVE_ANCHOR);
          ev.setProperty("eid", ed_.getID());
          ev.setProperty("anchor", draggingPoint_);
          ev.setProperty("from", oldDragPtLoc_);
          ev.setProperty("to", new Point2D.Double(draggingPoint_.getX(), draggingPoint_.getY()));
          cep_.handleEvent(ev);
        }
        dragMode_ = DragMode.NONE;
        draggingPoint_ = null;
      }

    });

    addMouseMotionListener(new MouseMotionAdapter() {

      @Override
      public void mouseDragged(MouseEvent e) {
        double dx = -coords_.dxToWorld(e.getX() - mx_);
        double dy = coords_.dyToWorld(e.getY() - my_);
        switch (dragMode_) {
          case PANNING:
            coords_.shiftRange(dx, dy);
            repaint();
            break;
          case DRAG_POINT:
            if(ed_.getBoolProperty(Editor.Property.SNAP_TO_GRID)) { //gui_.getControlPanel().getSnapToGrid()) {
              draggingPoint_.moveTo((double) Math.round(coords_.xToWorld(e.getX())),
                                    (double) Math.round(coords_.yToWorld(e.getY())));
            }
            else
              draggingPoint_.moveBy(-dx, -dy);
            ed_.getDocument().getNetwork().rebundle();
            repaint();
            break;
        }
        mx_ = e.getX();
        my_ = e.getY();
      }
    });

    addMouseWheelListener(this);
  }

  private void initCoords() {
    double yxRatio = (double) getHeight() / (double) getWidth();
    System.out.println("y/x="+yxRatio);
    double initX = 10;
    coords_ = new MapCoordinates(-initX, -yxRatio*initX, initX, yxRatio*initX, getWidth(), getHeight());
  }

  public MapCoordinates getCoordinates() {
    return coords_;
  }

  public Graphics2D getGraphics2D() {
    return g2d_;
  }

  private Color getBGColor() {
    return Color.white;
  }

  public int getClickTolerance() {
    return clickTol_;
  }
  
  public double getClickToleranceW() {
    return coords_.dxToWorld(clickTol_);
  }

  public void clear() {
    highlightedItems_ = new HashSet<Drawable>();
  }
  
  // HIGHLIGHT METHODS

  public void addHighlightedItem(Drawable item) {
    highlightedItems_.add(item);
  }

  public void removeHighlightedItem(Drawable item) {
    highlightedItems_.remove(item);
  }

  // ZOOM/PAN/RESIZE METHODS

  public void resized() {
    if(getWidth() == coords_.getWidth() && getHeight() == coords_.getHeight()) return;

    double mx = (coords_.getX2() + coords_.getX1()) / 2, my = (coords_.getY2() + coords_.getY1()) / 2;
    double hx = (coords_.getX2() - coords_.getX1()) / 2, hy = (coords_.getY2() - coords_.getY1()) / 2;
    double xRatio = (double) getWidth() / (double) coords_.getWidth();
    double yRatio = (double) getHeight() / (double) coords_.getHeight();

    double newX1 = mx - hx * xRatio;
    double newX2 = mx + hx * xRatio;
    double newY1 = my - hy * yRatio;
    double newY2 = my + hy * yRatio;

    coords_.updateRange(newX1, newY1, newX2, newY2);
    coords_.updateDim(getWidth(), getHeight());
    repaint();
  }

  public void zoom(double factor) {
    double dx = factor * (coords_.getX2() - coords_.getX1());
    double dy = factor * (coords_.getY2() - coords_.getY1());

    coords_.updateRange(coords_.getX1() + dx,
                        coords_.getY1() + dy,
                        coords_.getX2() - dx,
                        coords_.getY2() - dy);

    repaint();
  }

  // MOUSE INPUT METHODS

  private void leftClick(int mx, int my, boolean shift) {

    CartEvent event;
    double wx = coords_.xToWorld(mx), wy = coords_.yToWorld(my);

    switch(gui_.getToolbarAction()) {
      case Toolbar.CREATE_ANCHOR_POINT:
        fireClickEvent(CartEvent.Type.CREATE_ANCHOR, mx, my, true);

        /*double x = gui_.getControlPanel().getSnapToGrid() ? Math.round(wx) : wx;
        double y = gui_.getControlPanel().getSnapToGrid() ? Math.round(wy) : wy;

        event = new CartEvent(CartEvent.Type.CREATE_ANCHOR);
        event.setProperty("x", x);
        event.setProperty("y", y);
        cart_.handleEvent(event);*/
        break;

      case Toolbar.DELETE_ANCHOR_POINT:
        fireClickEvent(CartEvent.Type.DELETE_ANCHOR, mx, my, false);
        break;

      case Toolbar.CREATE_CORRIDOR:
        fireClickEvent(CartEvent.Type.CORRIDOR_ENDPOINT, mx, my, false);
        break;

      case Toolbar.DELETE_CORRIDOR:
        fireClickEvent(CartEvent.Type.DELETE_CORRIDOR, mx, my, false);
        break;

      case Toolbar.SPLIT_CORRIDOR:
        //cart_.getNetworkOps().splitCorridor(coords_.xToWorld(mx), coords_.yToWorld(my));
        //repaint();
        break;

      case Toolbar.MODIFY_LINE:
        //Corridor corr = network_.getCorridorAtXY(wx, wy, coords_.dxToWorld(clickTol_));
        //if(corr == null) break;
        event = new CartEvent(CartEvent.Type.MODIFY_LINE);
        //event.setProperty("corrID", corr.getID());
        event.setProperty("x", wx);
        event.setProperty("y", wy);
        cep_.handleEvent(event);
        repaint();
        break;
    }
  }

  private void rightClick(int mx, int my, boolean shift) {
    /*Corridor corr = network_.getCorridorAtXY(coords_.xToWorld(mx), coords_.yToWorld(my), coords_.dxToWorld(clickTol_));
    if(corr != null) {
      corr.flip();
      repaint();
    }*/
    fireClickEvent(CartEvent.Type.FLIP_CORRIDOR, mx, my, false);

  }

  public void fireClickEvent(CartEvent.Type type, int mx, int my, boolean snapApplies) {

    double wx = coords_.xToWorld(mx), wy = coords_.yToWorld(my);

    if(snapApplies) {
      wx = ed_.getBoolProperty(Editor.Property.SNAP_TO_GRID) ? Math.round(wx) : wx;
      wy = ed_.getBoolProperty(Editor.Property.SNAP_TO_GRID) ? Math.round(wy) : wy;
    }

    CartEvent event = new CartEvent(type);
    event.setProperty("eid", ed_.getID());
    event.setProperty("x", wx);
    event.setProperty("y", wy);
    cep_.handleEvent(event);
  }


  public void mouseWheelMoved(MouseWheelEvent e) {
    if(e.getWheelRotation() == -1) zoom(.1);
    if(e.getWheelRotation() == 1) zoom(-.1);
  }

  // PAINT METHODS

  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    if(coords_ == null) initCoords();

    g2d_ = (Graphics2D) g;

    // setup antialiasing
    if(ed_.getBoolProperty(Editor.Property.USE_ANTIALIASING)) { //gui_.getControlPanel().getAntialiasingOn()) {
      RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
      g2d_.setRenderingHints(renderHints);
    }

    g2d_.setColor(getBGColor());
    g2d_.fillRect(0, 0, getWidth(), getHeight());

    for(Drawable item : highlightedItems_) item.drawHighlight(this);

    if(ed_.getBoolProperty(Editor.Property.SHOW_GRID)) drawGrid();

    if(ed_.getBoolProperty(Editor.Property.SHOW_CORRIDORS))
      for(Corridor corr : ed_.getDocument().getNetwork().getCorridors()) corr.draw(this);

    if(ed_.getBoolProperty(Editor.Property.SHOW_LINES)) drawNetworkLines();

    if(ed_.getBoolProperty(Editor.Property.SHOW_ANCHORS))
      for(AnchorPoint pt : ed_.getDocument().getNetwork().getAnchorPoints()) pt.draw(this);


  }

  private void drawGrid() {
    g2d_.setColor(Color.lightGray);
    g2d_.setStroke(new BasicStroke(1));

    for(double x = Math.floor(coords_.getX1()); x <= Math.ceil(coords_.getX2()); x++)
      drawLineW(x, coords_.getY1(), x, coords_.getY2());

    for(double y = Math.floor(coords_.getY1()); y <= Math.ceil(coords_.getY2()); y++)
      drawLineW(coords_.getX1(), y, coords_.getX2(), y);
  }

  /**
   * Draws a line on the canvas given "world," rather than screen, coordinates.
   * 
   * @param x1
   * @param y1
   * @param x2
   * @param y2
   */
  public void drawLineW(double x1, double y1, double x2, double y2) {
    g2d_.drawLine(coords_.xToScreen(x1), coords_.yToScreen(y1),
                  coords_.xToScreen(x2), coords_.yToScreen(y2));
  }

  public void drawNetworkLines() {
    for(Line line : ed_.getDocument().getNetwork().getLines()) {
      //if(gui_.getControlFrameManager().getLinesFrame().lineEnabled(line))
      if(line.isEnabled())
        line.draw(this);
    }

    /*for(Corridor corr : network_.getCorridors()) {
      corr.drawLines(this);
    }*/
  }

}
